package mac10.roles;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;


import de.dailab.jiactng.agentcore.ontology.IAgentDescription;

import mac10.bean.MasterBean;
import mac10.models.Cell;
import mac10.models.Point;
import mac10.models.WorldModel;
import mac10.monitor.InfoMonitor;
import mac10.util.AStarHeapSwitcher;
import mac10.util.Node;

public class MasterSwitcher {
	
	static public MasterSwitcher staticMasterSwitcher;
	private WorldModel worldmodel; // TODO static worldmodel eintragen
	
	private Map<Cell, ArrayList<IAgentDescription>>  registry = Collections.synchronizedMap(new HashMap<Cell, ArrayList<IAgentDescription>>());
	private Map<Cell, IAgentDescription> switcher = Collections.synchronizedMap(new HashMap<Cell, IAgentDescription>());
	
	ArrayList<IAgentDescription> skipThisAgent = new ArrayList<IAgentDescription>();
	
	public MasterSwitcher(WorldModel worldmodel)
	{
		this.worldmodel = worldmodel;
	}

	//TODO fences für die kein switch bekannt is zwischenspeichern
	
	//Anmelden das man einen Fence passieren moechte
	public void register (IAgentDescription agent ,  Cell fenceCell)
	{
		Cell button = worldmodel.getFenceSwitch(fenceCell);
		if (button == null)
		{
			System.err.println("getFenceSwitch("+fenceCell.toStringShort()+") returned null");
			return;
		}
		synchronized (registry) 
		{
			ArrayList<IAgentDescription> list = registry.get(button);
			if (list == null) 
			{
				list = new ArrayList<IAgentDescription>();
				list.add(agent);
				registry.put(button, list);
//				System.out.println("Anmelden "+agent.getName() + " "+button.toStringShort());
			} 
			else if (!list.contains(agent)) 
			{
				list.add(agent);
			}
		}
				
	}
	
	public void deregister (IAgentDescription agent, Cell fenceCell)
	{

		Cell button = worldmodel.getFenceSwitch(fenceCell);
		synchronized(registry)
		{
			ArrayList<IAgentDescription> list = registry.get(button);			
			
			if(list == null)
			{
				System.err.println("Nicht in der Liste von der man sich abmeldet");				
			}
			else
			{
//				System.out.println(agent.getName()+" melde mich ab. passierte" + fenceCell.toStringShort()+"  size "+list.size());
				
				list.remove(agent);
				
				//entferne switch , keiner will mehr durch
				if(list.isEmpty())
				{
					System.out.println("keiner will mehr durch "+button);
					registry.remove(button);
				}
			}

		}
	}

    public void deregister (IAgentDescription agent)
    {
        synchronized (registry)
        {
            for (Iterator<ArrayList<IAgentDescription>> iterator = registry.values().iterator(); iterator.hasNext();)
            {
                ArrayList<IAgentDescription> list = (ArrayList<IAgentDescription>) iterator.next();
                if(list == null)
                    System.err.println("Das sollte nicht sein null liste in der HashMap");
                
                list.remove(agent);
                
                if (list.isEmpty())
                {
                    iterator.remove();
                }
            }
            
        }
    }

    
    
    public void deregisterSwitcher (IAgentDescription agent)
    {
        System.err.println("removed switcher der sich abgemeldet hat");

        for (Iterator<IAgentDescription> iterator = switcher.values().iterator(); iterator.hasNext();)
        {
            IAgentDescription   a = (IAgentDescription) iterator.next(); 
            if(a.equals(agent))
            {
                iterator.remove();
                break;
            }
            
        }
        
        skipThisAgent.add(agent);
    }
    
	public HashMap<IAgentDescription , Cell> currentSwitchers(Map<IAgentDescription, Cell> agents)
	{

        HashMap<IAgentDescription, Cell> data = new HashMap<IAgentDescription, Cell>();

        Map<IAgentDescription, Cell> possibleSwitcher = new HashMap<IAgentDescription, Cell>(
                agents);

        for (IAgentDescription  toBeRemoved : skipThisAgent)
        {
            possibleSwitcher.remove(toBeRemoved);
        }

        debugSwitcherInfo(agents);
        
		synchronized (registry) 
		{

		    // entfernt unbenötigte Switcher (kein Agent will durch)
		    // sonst entferne agents die bereits switcher sind aus possibleSwitcher
	        for (Iterator<Cell> iterator = switcher.keySet().iterator(); iterator.hasNext();)
            {
                Cell button = (Cell) iterator.next();

	            if(!registry.containsKey(button))
	            {
	                iterator.remove();
	            }
	            else
	            {
	                possibleSwitcher.remove(switcher.get(button));
	            }
	        }
		    
	        //TODO was passiert hier?
			for (Iterator<Cell> iterator = registry.keySet().iterator(); iterator.hasNext();)
            {
                Cell button = (Cell) iterator.next();
                ArrayList<IAgentDescription> list = registry.get(button);
                // es gibt noch keinen Switcher zu diesem Switch
				if (!switcher.containsKey(button)) 
				{
				    // wenn nur einer durch will soll er nicht selber switcher sein
				    
				    if(list.size() == 1)
				    {
    				    possibleSwitcher.remove(list.get(0));
    				    System.err.println("Swithcer entfernt der selber durch wollte");
				    }
				    
				    IAgentDescription a = findNearestAgent(button, possibleSwitcher);
					if (a != null) 
					{
						switcher.put(button, a);
						data.put(a, button);

					} 
					else 
					{
						System.err.println("kein switcher gefunden für "
								+ button);
					}

				} 
				// alten switcher beibehalten
				else 
				{
                    data.put(switcher.get(button), button);
				}
				
                // workaround
                // manchmal ist der agent noch auf seiner eigenen switch liste, ka warum
                list.remove(switcher.get(button));
				
			}
		}
		return data;
	}

	

	
	
    private IAgentDescription findNearestAgent (Cell button,
            Map<IAgentDescription, Cell> agents)
    {
    	if(agents.size() == 0)
    		return null;
    	
        int minDistance = Integer.MAX_VALUE, distance;
        final Cell goalCell = new Cell(button.getX(), button.getY(), -1);
        ArrayList<Point> pointsToAvoid = worldmodel.getFencesForSwitch(goalCell);
        Cell nearestCell = null;
        Cell agentPostions[] = agents.values().toArray(new Cell[agents.size()]);
        Arrays.sort(agentPostions, new Comparator<Cell>()
        {
            public int compare (Cell a, Cell b)
            {
                int distA = WorldModel.getDistance(a, goalCell);
                int distB = WorldModel.getDistance(b, goalCell);
                if (distA == distB)
                    return 0;
                else if (distA > distB)
                    return 1;
                else
                    return -1;
            }
        });
        
        int firstMaxNorm = WorldModel.getDistance(button, agentPostions[0]);
        
        for (int i = 0; i < agentPostions.length; i++)
        {
//            System.err.println("\t teste " + agentPostions[i] + " "+button);
            distance = WorldModel.getDistance(button, agentPostions[i]);
            
            if ( minDistance <= distance )
            {
                // es gibt keinen besseren switcher
                break;
            }
           
            Cell cellToOpenFence = this.worldmodel.findCellToOpenFence(
                    agentPostions[i], button);
            if(cellToOpenFence == null)
            {
                System.err.println("cells to open fence leer " + cellToOpenFence);
                return null;
            }            
            
            Node goal = existsPath(agentPostions[i], cellToOpenFence, // null vllt
                    MasterBean.getShortestDeadline() - 300, 
                    true, true, false, false, pointsToAvoid);
            
            if ( goal == null )
                continue;
            
            if(goal.getF() < minDistance )
            {
                minDistance = goal.getF();
                nearestCell = agentPostions[i];
                
                if(minDistance < 2 * firstMaxNorm)
                    break;
                
                if(MasterBean.getShortestDeadline() - 400 <= System.currentTimeMillis())
                    break;
            }
            
        }
        for (IAgentDescription agent : agents.keySet())
        {
            if (agents.get(agent).equals(nearestCell))
                return agent;
        }

        return null;
    }
	
	private Node existsPath(Cell start, Cell goal, long deadline,
            boolean avoidAgents, boolean avoidCows, boolean avoidOpenFences,
            boolean avoidClosedFences, ArrayList<Point> pointsToAvoid)
    {
	    AStarHeapSwitcher astar = new AStarHeapSwitcher(worldmodel, start, goal, pointsToAvoid);
        astar.setAvoidAgents(avoidAgents);
        astar.setAvoidCows(avoidCows);
        astar.setAvoidOpenFences(avoidOpenFences);
        astar.setAvoidClosedFences(avoidClosedFences);
        Node to = astar.AStar(deadline);
        return to;
    }

    private void debugSwitcherInfo(Map<IAgentDescription, Cell> agents)
	{
        agents = MasterBean.agents;
        InfoMonitor.staticMonitor.setLeftText("");
        for (Cell button : switcher.keySet())
        {
            InfoMonitor.staticMonitor.addLineToLeftText("Switch "
                    + button.toStringShort());
            IAgentDescription iAgentDescription = switcher.get(button);
            InfoMonitor.staticMonitor.addLineToLeftText("\t"
                    + iAgentDescription.getName() + " "
                    + agents.get(iAgentDescription).toStringShort());
        }
        
        InfoMonitor.staticMonitor.addLineToLeftText("\n\n-------------\n\n");
        for (Cell c : registry.keySet())
        {
            ArrayList<IAgentDescription> l = registry.get(c);
            InfoMonitor.staticMonitor.addLineToLeftText("Switch "
                    + c.toStringShort());

            for (IAgentDescription iAgentDescription : l)
            {
                InfoMonitor.staticMonitor.addLineToLeftText("\t"
                        + iAgentDescription.getName() + " "
                        + agents.get(iAgentDescription).toStringShort());
            }
        }
	}
}